/**
 * 
 */
package com.wgo.bpot.client.session;

import static com.wgo.bpot.client.session.LightSession.DEFAULT_UPDATE_DEPTH;
import static com.wgo.bpot.common.transport.util.MethodNamingConvention.PROXY_SUPER_INTERFACE;
//import static remato.common.session.light.LightSession.PROXY_SUPER_INTERFACE;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import com.wgo.bpot.common.transport.servicefacade.DispatchedModifyingMethodsTransport;
import com.wgo.bpot.common.transport.util.Assert;
import com.wgo.bpot.common.transport.util.MethodNamingConvention;



/**
 * @author petterei
 * @version $Id: DomainProxy.java,v 1.4 2006-01-24 12:11:23 petterei Exp $
 */
public interface DomainProxy<T> {

	/**
	 * Time before new update from server is triggered (if possible).
	 */
	public static final long CACHE_TIMEOUT = 1000 * 60 * 30; // 30 minutes

	/**
	 * Indicates if modifying methods is dispatched.
	 * 
	 * @return boolean if dirty or not.
	 */
	public boolean isDirty();
	
	/**
	 * @return The actual concept the proxy handles invokations for.
	 */
	public T getActualDomainModelObject();
	
	/**
	 * Returns all dispatched methods dispatched on the concept since last 
	 * retrieved from persistent store or created. Used for saving changes to 
	 * persistent store with fine granularity. Opposite methods, such as 
	 * 'add(instance)' and 'remove(instance)' are ignored. The methods linked 
	 * concepts to collect their dispatched methods.
	 *  
	 * @param dispatchedModifyingMethods The collection of dispatched methods 
	 * for all dirty concepts.
	 * @param proxy The initial proxy.
	 * /@/param traversalVisitedMark Used for efficient and accurate traversal of 
	 * the linked concepts.
	 */
	public void getDispatchedModifyingMethods(Map<DispatchedModifyingMethodsTransport, DomainProxy> dispatchedModifyingMethods, DomainProxy<T> proxy, Set<DomainProxy> backTrace);
	public T updateFromPersistentStore(T savedConcept, DomainProxy<T> proxy);
	public void cleanUpAfterSave(DomainProxy proxy);
	void update(T completeConcept, int updateDepth, T backTrace, DomainProxy proxy);
	void putDirtyMethod(Method method, Object arg, Object result);
}

class DomainProxyImpl<T> implements InvocationHandler, DomainProxy<T> {
	
	private static final Logger log = Logger.getLogger(DomainProxyImpl.class.getSimpleName());

	/**
	 * The actual concept the proxy handles invokations for.
	 */
	private T actualConcept = null;
	
	/**
	 * Preloaded Method objects for the methods in java.lang.Object, which is 
	 * target for special handeling.
	 */
	private static final Method hashCodeMethod;
	private static final Method equalsMethod;
	private static final Method toStringMethod;
	/**
	 * Ignore-method. Is not settable or ovveridable.
	 */
	private static final Method getClassMethod;
	/**
	 * Used for marking new concepts dirty. 
	 */
	private Method getIdentifierMethod = null; // could cache this result to increase performance
	
	static {  // preloading the methods
		try {
			hashCodeMethod = Object.class.getMethod("hashCode", (Class[]) null);
			equalsMethod = Object.class.getMethod("equals", new Class[] { Object.class });
			toStringMethod = Object.class.getMethod("toString", (Class[]) null);
			getClassMethod = Object.class.getMethod("getClass", (Class[]) null);
		} catch (NoSuchMethodException e) {
			throw new NoSuchMethodError(e.getMessage());
		}
	}
	
	/**
	 * Map from getter- to setter-methods handeled by this proxy. The map are
	 * preloaded at initialization to optiamalize performance. The map is used
	 * during merge new information from server, among others.
	 */
	private Map<Method, Method> allDeclaredByInterfaceToRealGetter = new Hashtable<Method, Method>();

	/**
	 * Maps real getter- to real setter-methods. Used for updates. 
	 */
	private Map<Method, Method> realGetterToRealSetter = new Hashtable<Method, Method>();
	
	/**
	 * The Long-value indicates a timestamp, representing the time the 
	 * information this get-method returns last were retrieved from the server.
	 */
	private Map<Method, Long> cachedMethodResults = new Hashtable<Method,Long>();

	/**
	 * Used to ensure that unsaved modified changes is not overwritten from
	 * server. Further, to arrange for server updates with fine granularity.
	 */
	private Set<Method> dirtyRealGetters = new HashSet<Method>() ;
	
	/**
	 * Used to ensure that unsaved modified changes is not overwritten from
	 * server. Further, to arrange for server updates with fine granularity.
	 */
	private Set<Method> accessedRealGetters = new HashSet<Method>() ;
	
	/**
	 * Defines single relations to other concepts. Used for model travesal 
	 * among other things.
	 */
	private Set<Method> linkToOtherConcept = new HashSet<Method>();

	/**
	 * Defines multiple relations to other concepts. Used for model travesal 
	 * among other things.
	 */
	private Set<Method> linkToOtherConcepts = new HashSet<Method>();
	
	private Class[] interfaces = null;
	
	private Class actualClass = null;

	/**
	 * New or all methods dirty.
	 */
	private boolean isRetrievalFromServerPossible = true;
	
	private LightSession session = null;
	
	private DispatchedModifyingMethodsCache<T> dispatchedModificationMethods = new DispatchedModifyingMethodsCache<T>();
	
	private boolean partial = true;

	private volatile static Map<String,DomainProxy> identifierToProxy = new Hashtable<String,DomainProxy>(); // could move this to session..

	/**
	 * Internal static registry to be able to replace actual concept parameters
	 * with the corresponding proxy. The map is from actual concept to the 
	 * concept proxy.
	 */
	private volatile static Hashtable<Object, DomainProxy> cachedParameters = new Hashtable<Object,DomainProxy>(); //TODO: could make this threadlocal and skip volatile

	public static <S> DomainProxy<S> newInstance(S actualDomainModelObject, LightSession session, Method getIdentifierMethod, int updateDepth) {
		return newInstance(actualDomainModelObject, session, getIdentifierMethod, updateDepth, null);
	}
	
	@SuppressWarnings("unchecked")
	private synchronized static <S> DomainProxy<S> newInstance(S actualDomainModelObject, LightSession session, Method getIdentifierMethod, int updateDepth, S backTrace) {
		Assert.assertNotNull(actualDomainModelObject, "Cannot create proxy, actual concept was null.");
		Assert.assertTrue(!(actualDomainModelObject instanceof DomainProxy), "Cannot proxy, actual concept was already a proxy.");
		Long conceptIdentifier = (Long)internalInvoke(actualDomainModelObject, getIdentifierMethod, "Could not invoke getIdentifier method on actual concept");
		String registryIdentifier = actualDomainModelObject.getClass().getSimpleName()+conceptIdentifier;
//		log.info("Identifier: " + registryIdentifier);
		if (null != conceptIdentifier && identifierToProxy.containsKey(registryIdentifier)) {
			DomainProxy<S> result = (DomainProxy<S>)identifierToProxy.get(registryIdentifier); //TODO: verify that the actual concepts has equal classes..
			result.update(actualDomainModelObject, updateDepth, backTrace, result);
			return result;
		}
		Class[] domainInterfaces = actualDomainModelObject.getClass().getInterfaces();
		List<Class> interfaces = new ArrayList<Class>(domainInterfaces.length + 1);
		interfaces.addAll(Arrays.<Class>asList(domainInterfaces));
		interfaces.add(DomainProxy.class);
		DomainProxy<S> newProxy = (DomainProxy<S>)java.lang.reflect.Proxy.newProxyInstance(
				actualDomainModelObject.getClass().getClassLoader(),
				interfaces.toArray(new Class[interfaces.size()]),
				new DomainProxyImpl<S>(actualDomainModelObject, session, getIdentifierMethod));
		if (null != conceptIdentifier) {
			identifierToProxy.put(registryIdentifier,newProxy);
		}
		newProxy.update(actualDomainModelObject, updateDepth, backTrace, newProxy);
		return newProxy;
	}
	
	private DomainProxyImpl(T newActualDomainModelObject, LightSession newSession, Method getIdentifierMethod) {
		this.actualConcept = newActualDomainModelObject;
		this.interfaces = actualConcept.getClass().getInterfaces();
		this.getIdentifierMethod = getIdentifierMethod;
		this.session = newSession;
		this.actualClass = actualConcept.getClass();
		for (Class clazz : interfaces) { //TODO: check if real methods are public methods ?
			for (Method method : clazz.getMethods()) {
				MethodNamingConvention methodType = MethodNamingConvention.getMethodType(method);
				Method realGetter = methodType.retriveGetterMethod(actualClass, method);
				if (getClassMethod != realGetter) {
					Method realSetter = methodType.retriveSetterMethod(actualClass, method);
					allDeclaredByInterfaceToRealGetter.put(method,realGetter);
					Method inconsistentCheck = realGetterToRealSetter.put(realGetter, realSetter);
					if (null != inconsistentCheck) {
						if (!inconsistentCheck.equals(realSetter)) {
							if (inconsistentCheck.getParameterTypes()[0] != realSetter.getParameterTypes()[0]) {
								throw new InternalError("Inconsistency in declared methods in interfaces vs. actual class. Method: "+method.getName()+", real getter: " + realGetter.getName()+ ", real setter: " + realSetter.getName());
							}
							log.warning("Two equal, but different setters found for the same getter. Method: "+method.getName()+", real getter: " + realGetter.getName()+ ", real setter: " + realSetter.getName());
						}
					} else {
						if (MethodNamingConvention.isActualLinkToConcept(realGetter)) {
							linkToOtherConcept.add(realGetter);
						} else if (MethodNamingConvention.isActualLinkToConcepts(realGetter)) {
							linkToOtherConcepts.add(realGetter);
						}
					}
					methodType.validate(method);
				} else {
					log.warning("'getClass' should not be present in interfaces.");
				}
			}
		}
		HashSet<String> methodNames = new HashSet<String>();
		for (Method method : realGetterToRealSetter.keySet()) {
			if (! methodNames.add(method.getName())) {
				log.warning("Duplicate method-names found: " + method.getName());
			}
		}
		if (null == internalInvoke(actualConcept, getIdentifierMethod, "Could not invoke getIdentifier method on actual concept")) {
			markDirty(getIdentifierMethod);
			isRetrievalFromServerPossible = false;
			partial = false;
			dispatchedModificationMethods.setTargetConcept(actualConcept);
		}
	}
	
	/**
	 * @return Returns the actualDomainModelObject.
	 */
	public T getActualDomainModelObject() {
		return actualConcept;
	}
	
	protected static void init () {
		cachedParameters.clear();
		cachedParameters = new Hashtable<Object,DomainProxy>();
		identifierToProxy.clear();
		identifierToProxy = new Hashtable<String,DomainProxy>();
	}

	/**
	 * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
	 */
	@SuppressWarnings("unchecked")
	public Object invoke(Object proxy, Method method, Object[] args)
	throws Throwable {
		
		if (!(Proxy.getInvocationHandler(proxy).equals(this))) {
			throw new InternalError("proxy unknown");
		}
		
		Class declaringClass = method.getDeclaringClass();
		if (declaringClass == Object.class) {
			if (method.equals(hashCodeMethod)) {
				return proxyHashCode(proxy);
			} else if (method.equals(equalsMethod)) {
				if (null != args[0] && PROXY_SUPER_INTERFACE.isAssignableFrom(args[0].getClass()) && ! isProxyType(args[0])) {
					args[0] = cachedParameters.get(args[0]);
					if (null == args[0]) {
						throw new DomainSessionException("Could not find proxy for actual-concept.. (equals method..)");
					}
				}
				return proxyEquals(proxy, args[0]);
			} else if (method.equals(toStringMethod)) {
				return proxyToString(proxy);
			} else {
				throw new InternalError(
						"unexpected Object method dispatched: " + method);
			}
		} else if (declaringClass == DomainProxy.class) {
			try {
				return method.invoke(this, args);
			} catch (InvocationTargetException e) {
				throw e.getTargetException();
			}
		}

		for (int i = 0; i < interfaces.length; i++) {
			if (declaringClass.isAssignableFrom(interfaces[i])) {
				try {
					MethodNamingConvention methodType = MethodNamingConvention.getMethodType(method);
					Method realGetter = methodType.retriveGetterMethod(actualConcept.getClass(), method);
					boolean wasCached = preInvokeMethod(method, args, realGetter, proxy);
					Object result = method.invoke(actualConcept, args);
					postInvokeMethod(realGetter, methodType, method, args, wasCached, result);
					return result;
				} catch (InvocationTargetException e) {
					throw e.getTargetException();
				}
			}
		}
		throw new InternalError("unexpected method dispatched: " + method);
	}
	
	private Integer proxyHashCode(Object proxy) {
		return new Integer(System.identityHashCode(proxy));
	}
	
	private Boolean proxyEquals(Object proxy, Object other) {
		return (proxy == other ? Boolean.TRUE : Boolean.FALSE);
	}
	
	private String proxyToString(Object proxy) {
		return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode());
	}
	
	private boolean preInvokeMethod(Method method, Object[] args, Method realGetter, Object proxy) {
		boolean wasChached = false;
		Class[] methodParameterTypes = method.getParameterTypes();
		if ( MethodNamingConvention.isMethodParameterOfConceptType(methodParameterTypes) ||
				MethodNamingConvention.isLinkToConceptOrConcepts(method)) { //validate collection type?)
			if (0 < methodParameterTypes.length) {
				if (null == cachedParameters.put(actualConcept, (DomainProxy)proxy)){
					wasChached = true;
				}
				verifyProxyObjectTypes(args);
			}
			synchronized (proxy) { // ensure only this thread accesses the proxy if an update is necessary and it is updated
				if (isRetrievealFromServerNecessary(realGetter)) {
					T actual = retrieveCompletePersistentFromServer();
					update(actual, DEFAULT_UPDATE_DEPTH, null, (DomainProxy)proxy);
				}
			}
		}
		return wasChached;
	}

	private void postInvokeMethod(Method realGetter, MethodNamingConvention methodType, Method actualMethod, Object[] args, boolean wasCached, Object result) {
		if  (MethodNamingConvention.MODIFICATION_METHOD_TYPES.contains(methodType)) {
			markDirty(realGetter);
			dispatchedModificationMethods.updateModifyingMethods(methodType, actualMethod, args, result);
		}
		if (wasCached) { //TODO: move to final clause in invoke method?
			cachedParameters.remove(actualConcept);
		}
	}
	
	public boolean isDirty() {
		return ! dirtyRealGetters.isEmpty();
	}
	
	private void markDirty(Method realGetter) {
//		if (dirtyRealGetters.isEmpty()) {
			session.markDirty();
//		}
		dirtyRealGetters.add(realGetter);
	}
	
	@SuppressWarnings("unchecked")
	private void verifyProxyObjectTypes(Object[] proxies) {  // TODO: make sure all all parameters to this method is assignable from proxy super interface, then it will be possible to skip some tests..
		for (int i = 0; i < proxies.length; i++) {
			if (null != proxies[i] && ! isProxyType(proxies[i])) {
				Object oldValue = proxies[i];
				proxies[i] = cachedParameters.get(proxies[i]);
				if (null == proxies[i]) {
					if (PROXY_SUPER_INTERFACE.isAssignableFrom(oldValue.getClass())) {
						throw new InternalError("Expected proxy was not proxy afterall, type: " + oldValue.getClass().getSimpleName());
					}
					proxies[i] = oldValue;
				}
			}
		}
	}
	
	public static boolean isProxyType(Object unknown) {
		return (unknown instanceof DomainProxy);
//		return Proxy.isProxyClass(unknown.getClass());
	}
	
	private boolean isRetrievealFromServerNecessary(Method realGetter) {
		if (partial) {
			return true;
		}
		if (! isRetrievalFromServerPossible || dirtyRealGetters.contains(realGetter)) {
			return false;
		}
		if ((System.currentTimeMillis() - cachedMethodResults.get(realGetter).longValue()) < CACHE_TIMEOUT) {
			return false;
		}
		//TODO: check if possible (any mothods not dirty..) and update 'isRetrievalFromServerPossible'
		return true;
	}
	
	@SuppressWarnings("unchecked")
	private T retrieveCompletePersistentFromServer() {
		T result = null;
		try {
			result = (T)session.getService().retrieveCompletePersistent(actualConcept.getClass().getInterfaces()[0], getConceptIdentifier(), DEFAULT_UPDATE_DEPTH);
		} catch (Throwable e) {
			throw new DomainSessionException("Retrieval of information from server failed.", e);
		}
		return result;
	}

	/**
	 * iterate over all methods - dirty methods - accessed methods and invoke and update timestamps
	 * iterate over accessed methods - dirty methods and add new contents and update timestamps
	 */
	public void update(T completeConcept, int updateDepth, T backTrace, DomainProxy theProxy) { // TODO: lock or synchronize?
		if (0 >= updateDepth || actualConcept.equals(backTrace)) {
			return;
		}
		updateDepth--;
		if (actualConcept.getClass() != completeConcept.getClass()) {
			throw new DomainSessionException("Concept can only be replaced by a saved concept of same class. Existing class:" + actualConcept.getClass() + " New class: " + completeConcept.getClass());
		}
// TODO: implement update with finer granularity to avoid overwrite dirty or accessed information at cache timeout..
//		HashSet<Method> bruteForceMethods = new HashSet<Method>();
//		HashSet<Method> mergeMethods = new HashSet<Method>();
//		for (Method method : realGetterToRealSetter.keySet()) {
//			if (! dirtyRealGetters.contains(method)) {
//				if (! accessedRealGetters.contains(method) || ! MethodNamingConvention.isLinkToConceptOrConcepts(method)) {
//					bruteForceMethods.add(method);
//				} else {
//					mergeMethods.add(method);
//				}
//			}
//		}
//		for (Method method : bruteForceMethods ) {
//			// todo: brute force
//		}
//		for (Method method : bruteForceMethods ) {
//			//todo: merge..
//		}
		// temporary update links (brute forces all)
		boolean removeProxyFromCache = false;
		if (null != theProxy && null == cachedParameters.put(actualConcept, theProxy)) { // TODO: fix 'theProxy' always true.. (or remove..)
			removeProxyFromCache = true;
		}
		for (Method method : linkToOtherConcept) {
			Object result = internalInvoke(completeConcept, method, "Could not retrieve link from updated concept..");
			if (null != result) {
				DomainProxy linkProxy = newInstance(result, session, getIdentifierMethod, updateDepth, actualConcept );
				boolean removeLinkProxyFromCache = false;
				if (! isProxyType(result) && null == cachedParameters.put(result, linkProxy)) {
					removeLinkProxyFromCache = true;
				}
				updateLink(method, linkProxy);
				if (removeLinkProxyFromCache) {
					cachedParameters.remove(result);
				}
			} else {
				cachedMethodResults.put(method, new Long(System.currentTimeMillis()));
			}
		}
		for (Method method : linkToOtherConcepts) {
			Object resultCollection = internalInvoke(completeConcept, method, "Error when invoking link-to-other concepts method.");
			if (resultCollection instanceof Collection) {
				Collection<DomainProxy> newCollection = null;
				if (Set.class.isAssignableFrom(resultCollection.getClass())) {
					newCollection = new HashSet<DomainProxy>();
				} else if (List.class.isAssignableFrom(resultCollection.getClass())) {
					newCollection = new ArrayList<DomainProxy>();
				} else {
					throw new DomainSessionException("Unknow collection type: " + resultCollection.getClass().getSimpleName());
				}
				for (Object result : (Collection)resultCollection ) {
					if (null != result) {
						DomainProxy proxy = newInstance(result, session, getIdentifierMethod, updateDepth, actualConcept);
						newCollection.add(proxy);
					} else {
						log.info("Update: collection had null element.."); // TODO: remove temporary debug
					}
				}
				updateLink(method, newCollection );
			} else {
				String debugMessage = "Invalid type of linked-to-concepts method. Expected result type: " + Collection.class.getSimpleName();
				if (null != resultCollection) {
					debugMessage += "Actual result type was: " + resultCollection.getClass().getSimpleName();
				} else {
					debugMessage += "Method returned 'null'";
				}
				throw new DomainSessionException (debugMessage) ;
			}
		}
		if (removeProxyFromCache) { // remove here to keep small
			cachedParameters.remove(actualConcept);
		}
		partial = false;
	}

	private Long getConceptIdentifier() {
		return (Long)internalInvoke(actualConcept, getIdentifierMethod, "Could not invoke getIdentifier method on actual concept");
	}

	public void getDispatchedModifyingMethods(Map<DispatchedModifyingMethodsTransport, DomainProxy> dispatchedModifyingMethodsCollection, DomainProxy<T> proxy, Set<DomainProxy> backTrace) {
//		if ( this.visitedMark == traversalVisitedMark ) { // using a boolean will increase performance, but then it becomes dependent on the current state.. could also use int
//			return;
//		}
//		this.visitedMark = traversalVisitedMark;
		// totally failsafe implementation:
		if (backTrace.contains(proxy)) {
			return;
		}
		backTrace.add(proxy);
		if (isDirty()) {
			DispatchedModifyingMethodsTransport transport = this.dispatchedModificationMethods.getTransport();
			if (null != transport) {
				transport.setTargetConcept(PROXY_SUPER_INTERFACE.cast(actualConcept));
				dispatchedModifyingMethodsCollection.put(transport, proxy);
			}
		}
		if (partial) {
			return;
		}
		// TODO use traverseLinksToOtherConcepts()?
		for (Method method : linkToOtherConcept) {
			Object result = internalInvoke(actualConcept, method, "Error when invoking link-to-other concepts method.");
			internalGetDispatchedModifyingMethods(dispatchedModifyingMethodsCollection, result, backTrace);
		}
		for (Method method : linkToOtherConcepts) {
			Object resultCollection = internalInvoke(actualConcept, method, "Error when invoking link-to-other concepts method.");
			if (resultCollection instanceof Collection) {
				for (Object result : (Collection)resultCollection ) {
					internalGetDispatchedModifyingMethods(dispatchedModifyingMethodsCollection, result, backTrace);
				}
			} else {
				String debugMessage = "Invalid type of linked-to-concepts method. Expected result type: " + Collection.class.getSimpleName();
				if (null != resultCollection) {
					debugMessage += "Actual result type was: " + resultCollection.getClass().getSimpleName();
				} else {
					debugMessage += "Method returned 'null'";
				}
				throw new DomainSessionException (debugMessage) ;
			}
		}
	}
	
	@SuppressWarnings("unchecked")
	private void internalGetDispatchedModifyingMethods(Map<DispatchedModifyingMethodsTransport, DomainProxy> dispatchedModifyingMethodsCollection, Object result, Set<DomainProxy> backTrace) {
		if (isProxyType(result) && MethodNamingConvention.isMethodParameterOfConceptType(new Class[]{result.getClass()})) {
			DomainProxy<T> linkedProxy = (DomainProxy<T>)result;
			linkedProxy.getDispatchedModifyingMethods(dispatchedModifyingMethodsCollection, linkedProxy, backTrace);
		} else if (null != result) {
			throw new DomainSessionException ( "Invalid type of linked to method: " + result.getClass().getSimpleName() ) ;
		}
	}
	
	private static <S> Object internalInvoke(S targetObject, Method method, String debugMessage, Object... args) {
		Assert.assertNotNull(targetObject, debugMessage + " Target instance cannot be 'null'.");
		Assert.assertNotNull(method, debugMessage + " Method cannot be 'null'.");
		Object result = null;
		Throwable throwable = null;
		try {
			result = method.invoke(targetObject, args);
		} catch (InvocationTargetException e) {
			throwable = e.getTargetException();
		} catch (Throwable e) {
			throwable = e;
		}
		if (null != throwable) {
			throw new DomainSessionException(debugMessage + " Target instance type: " + targetObject.getClass() + " Method: " + method.getName(), throwable);
		}
		return result;
	}

	public void cleanUpAfterSave(DomainProxy proxy) { //TODO:
		Assert.warnIfTrue(partial,getClass().getSimpleName() + ": Saved concept("+ actualClass + getConceptIdentifier() +") are not partial with normal use, which client are you using?"); // if using the client, then they are not.. but if using test, not accessing links/relation, then it can..
		this.dispatchedModificationMethods = new DispatchedModifyingMethodsCache<T>();
		dirtyRealGetters.clear();
		accessedRealGetters.clear();
		update(actualConcept, 1, null, proxy);
	}

	private void updateLink(Method getterMethod, Object argument) {
		internalInvoke(actualConcept,realGetterToRealSetter.get(getterMethod), "Could not update field.", argument);
		cachedMethodResults.put(getterMethod, new Long (System.currentTimeMillis()));
	}
	
	public T updateFromPersistentStore(T savedConcept, DomainProxy<T> proxy) {
		Long identifier = (Long)internalInvoke(savedConcept, getIdentifierMethod, "Could not invoke identifier method on saved concept.");
		Assert.assertNotNull(identifier, "Saved concept has noe identifier..");
		Assert.assertTrue(actualConcept.getClass().equals(savedConcept.getClass()), "Concept can only be replaced by a saved concept of same class. Existing class:" + actualConcept.getClass() + " New class: " + savedConcept.getClass());
		T old = actualConcept;
		actualConcept = savedConcept;
		String registryIdentifier = savedConcept.getClass().getSimpleName()+identifier;
		DomainProxy registeredProxy = identifierToProxy.get(registryIdentifier);
		if (null == registeredProxy) {
			identifierToProxy.put(registryIdentifier, proxy);
		} else if (! proxy.equals(registeredProxy)){
			throw new DomainSessionException("Inconsistent proxyregister.. (duplicate proxies, dbId: "+identifier+")");
		}
		return old;
	}

	public void putDirtyMethod(Method method, Object arg, Object result) {
		MethodNamingConvention methodType = MethodNamingConvention.getMethodType(method);
		internalInvoke(actualConcept,method, "Could not set " + arg , arg);
		dispatchedModificationMethods.updateModifyingMethods(methodType, method, new Object[] {arg}, result);
	}

}
